/*
 * Copyright (C) 2020 Agrui
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ParameterCheck.h"
#include "OSMemory.h"
#include "IpcChannel.h"
#include "IpcChannelManager.h"



typedef struct {
    int id;
    int state;
} ChannelStateInfo;

typedef struct {
    int id;
    void* callbackData;
    onChannelStateChangedCallback callback;
} ChannelStateCallbackInfo;

typedef struct {
    int id;
    void* data;
    int dataLen;
    void* callbackData;
    onDataReceiveCallback callback;
} ChannelDataReceiveCallbackInfo;

static IpcChannelManager* channelManager = NULL;

IpcChannelManager* newIpcChannelManager() {
    IpcChannelManager* manager = OS_ALLOC_AND_RESET(IpcChannelManager);
    PARAMTER_CHECK_RETURN_NULL(manager);
    manager->channelList = newArrayList();
    if (!manager->channelList) {
        OS_FREE(manager);
        return NULL;
    }
    manager->channelStateRegList = newArrayList();
    if (!manager->channelStateRegList) {
        deleteArrayList(manager->channelList);
        OS_FREE(manager);
        return NULL;
    }

    manager->channelDataRegList = newArrayList();
    if (!manager->channelDataRegList) {
        deleteArrayList(manager->channelList);
        deleteArrayList(manager->channelStateRegList);
        OS_FREE(manager);
        return NULL;
    }

    channelManager = manager;
    return manager;
}

IpcChannelManager* getDefaultIpcChannelManager() {
    return channelManager;
}

boolean enumStateRegisterCallback(ArrayList* list, void* itemData, void* userData) {
    ChannelStateCallbackInfo* info = (ChannelStateCallbackInfo*)itemData;
    ChannelStateInfo* stateInfo = (ChannelStateInfo*)userData;
    PARAMTER_CHECK_RETURN_VALUE(info && stateInfo, TRUE);
    if (info->id == stateInfo->id) {
        info->callback(info->callbackData, info->id, stateInfo->state);
    }

    return TRUE;
}

static void onChannelStateChanged(void* callbackData, int id, int state) {
    IpcChannelManager* manager = (IpcChannelManager*)callbackData;
    ChannelStateInfo stateInfo;
    stateInfo.id = id;
    stateInfo.state = state;
    arrayListEnum(manager->channelStateRegList, enumStateRegisterCallback, &stateInfo);
}

boolean enumDataReceiveCallback(ArrayList* list, void* itemData, void* userData) {
    ChannelDataReceiveCallbackInfo* regInfo = (ChannelDataReceiveCallbackInfo*)itemData;
    ChannelDataReceiveCallbackInfo* dataInfo = (ChannelDataReceiveCallbackInfo*)userData;
    PARAMTER_CHECK_RETURN_VALUE(regInfo && dataInfo, TRUE);
    if (regInfo->id == dataInfo->id) {
        regInfo->callback(regInfo->callbackData, dataInfo->id, dataInfo->data, dataInfo->dataLen);
    }

    return TRUE;
}

static void onChannelDataReceive(void* callbackData, int id, void* data, int dataLen) {
    IpcChannelManager* manager = (IpcChannelManager*)callbackData;
    ChannelDataReceiveCallbackInfo dataInfo;
    dataInfo.id = id;
    dataInfo.data = data;
    dataInfo.dataLen = dataLen;
    arrayListEnum(manager->channelDataRegList, enumDataReceiveCallback, &dataInfo);
}

void addChannel(IpcChannelManager* manager, IpcChannel* channel) {
    PARAMTER_CHECK_RETURN_VOID(manager && channel && manager->channelList);
    arrayListAddKey(manager->channelList, channel->channelId, channel);
    registerChannelStateChanged(channel, onChannelStateChanged, manager);
    registerChannelReceiveCallback(channel, onChannelDataReceive, manager);
}

void registerIpcChannelStateChanged(IpcChannelManager* manager, int channelId, onChannelStateChangedCallback callback, void* callbackData) {
    ChannelStateCallbackInfo* info = (ChannelStateCallbackInfo*)OS_MALLOC(sizeof(ChannelStateCallbackInfo));
    PARAMTER_CHECK_RETURN_VOID(info);
    info->id = channelId;
    info->callback = callback;
    info->callbackData = callbackData;
    arrayListAdd(manager->channelStateRegList, info);
}

static boolean matchChannelInList(ArrayList* list, void* itemData, void* userData) {
    ChannelStateCallbackInfo* info = (ChannelStateCallbackInfo*)itemData;
    PARAMTER_CHECK_RETURN_VALUE(info && userData, FALSE);
    int channelId = *(int*)(userData);
    if (channelId == info->id) {
        return TRUE;
    }

    return FALSE;
}

void unregisterIpcChannelStateChanged(IpcChannelManager* manager, int channelId) {
    PARAMTER_CHECK_RETURN_VOID(manager && manager->channelStateRegList);
    arrayListRemoveItems(manager->channelStateRegList, matchChannelInList, OSFree, &channelId);
}

void registerIpcChannelDataReceiveInd(IpcChannelManager* manager, int channelId, onDataReceiveCallback callback, void* callbackData) {
    ChannelDataReceiveCallbackInfo* info = (ChannelDataReceiveCallbackInfo*)OS_MALLOC(sizeof(ChannelDataReceiveCallbackInfo));
    PARAMTER_CHECK_RETURN_VOID(info);
    info->id = channelId;
    info->callback = callback;
    info->callbackData = callbackData;
    arrayListAdd(manager->channelDataRegList, info);
}

void unregisterIpcChannelDataReceiveInd(IpcChannelManager* manager, int channelId) {
    PARAMTER_CHECK_RETURN_VOID(manager && manager->channelDataRegList);
    arrayListRemoveItems(manager->channelDataRegList, matchChannelInList, OSFree, &channelId);
}

void removeIpcChannel(IpcChannelManager* manager, IpcChannel* channel) {
    PARAMTER_CHECK_RETURN_VOID(manager && channel && manager->channelList);
    unregisterChannelReceiveCallback(channel);
    unregisterChannelStateChanged(channel);
    arrayListRemove(manager->channelList, channel);
}

IpcChannel* getChannel(IpcChannelManager* manager, int channelId) {
    PARAMTER_CHECK_RETURN_NULL(manager);
    return (IpcChannel*)arrayListGetKeyData(manager->channelList, channelId);
}

static void deleteChannelCallback(void* itemData) {
    PARAMTER_CHECK_RETURN_VOID(itemData);
    IpcChannel* channel = (IpcChannel*)itemData;
    deleteIpcChannel(channel);
}

void deleteIpcChannelManager(IpcChannelManager* manager) {
    if (manager) {
        if (manager->channelList) {
            arrayListRemoveAll(manager->channelList, deleteChannelCallback);
            deleteArrayList(manager->channelList);
        }
        if (manager->channelStateRegList) {
            arrayListRemoveAll(manager->channelStateRegList, OSFree);
            deleteArrayList(manager->channelStateRegList);
        }

        if (manager->channelDataRegList) {
            arrayListRemoveAll(manager->channelDataRegList, OSFree);
            deleteArrayList(manager->channelDataRegList);
        }

        OS_FREE(manager);
        channelManager = NULL;
    }
}

